from sympy import Basic, Dummy, MutableDenseMatrix, acos, sqrt, sympify, S, Identity
from sympy.matrices import MatrixExpr, MatrixSymbol, ZeroMatrix
from sympy.core.symbol import Str
from sympy.matrices.expressions.matexpr import MatrixElement, _LeftRightArgs

"""
this class is copied from MatrixSymbol.
"""


class Vec(MatrixExpr):
    """This is a class to imita the abstrct vector."""

    is_commutative = False
    is_symbol = True
    _diff_wrt = True

    def __new__(cls, name, value):
        if isinstance(name, str):
            name = Str(name)
        obj = Basic.__new__(cls, name, value)
        return obj

    @property
    def value(self):
        return self.args[1]

    @property
    def shape(self):
        return self.args[1].shape

    @property
    def name(self):
        return self.args[0].name

    def _entry(self, i, j, **kwargs):
        return MatrixElement(self, i, j)

    @property
    def free_symbols(self):
        return {self}

    def _eval_simplify(self, **kwargs):
        return self

    def _eval_derivative(self, x):
        # x is a scalar:
        return ZeroMatrix(self.shape[0], self.shape[1])

    def _eval_derivative_matrix_lines(self, x):
        if self != x:
            first = (
                ZeroMatrix(x.shape[0], self.shape[0]) if self.shape[0] != 1 else S.Zero
            )
            second = (
                ZeroMatrix(x.shape[1], self.shape[1]) if self.shape[1] != 1 else S.Zero
            )
            return [
                _LeftRightArgs(
                    [first, second],
                )
            ]
        else:
            first = Identity(self.shape[0]) if self.shape[0] != 1 else S.One
            second = Identity(self.shape[1]) if self.shape[1] != 1 else S.One
            return [
                _LeftRightArgs(
                    [first, second],
                )
            ]


def vecs_angle(vec1: MutableDenseMatrix, vec2: MutableDenseMatrix):
    length_vec1 = sqrt(vec1.value.dot(vec1.value))
    length_vec2 = sqrt(vec2.value.dot(vec2.value))
    dot_time = vec1.value.dot(vec2.value)
    return acos(dot_time / (length_vec1 * length_vec2))
